/*******************************************************************
 *
 * Copyright (C), 2021-2022, LS, All rights reserved.
 *
 * Author      : 
 * Create Time : 2020-12-15
 * Version     : v0.1
 * Description : 
 *******************************************************************/
 
/*
 * INCLUDE FILES
 *****************************************************************************************
 */
#include "ls_ble_log.h"

#if LS_BLE_LOG_ENABLE

#include <string.h>
#include <stdarg.h>
#include <stdio.h>

#if APP_LOG_USE_BSP_CFG_ENABLE
#include "log.h"
#endif

/*
 * DEFINE
 *****************************************************************************************
 */

/*
 * STRUCTURES
 *****************************************************************************************
 */
/**@brief App log environment variable. */
struct ls_ble_log_env_t
{
    ls_ble_log_init_t       app_log_init;
    bool                    is_filter_set;
    bool                    is_output_api_cfg;
    ls_ble_log_trans_func_t trans_func;
    ls_ble_log_flush_func_t flush_func;
};

/*
 * LOCAL VARIABLE DEFINITIONS
 *****************************************************************************************
 */
static uint8_t s_log_encode_buf[LS_BLE_LOG_LINE_BUF_SIZE];  /**< App log data encode buffer. */

static const char *s_log_svt_lvl_output_info[] =            /**< App log severity level outpout information. */
{
    [LOG_LVL_ERROR]   = "APP_E: ",
    [LOG_LVL_WARNING] = "APP_W: ",
    [LOG_LVL_INFO]    = "APP_I: ",
    [LOG_LVL_DEBUG]   = "APP_D: ",
};

static struct ls_ble_log_env_t  s_app_log_env;                  /**< App log environment variable. */


/*
 * LOCAL FUNCTION DEFINITIONS
 *****************************************************************************************
 */
/**
 *****************************************************************************************
 * @brief App log string copy.
 *
 * @param[in] wr_idx:     Write index of app log buffer.
 * @param[in] p_log_buff: Pointer to app log cache buffer.
 * @param[in] p_log_data: Pointer to app log data.
 *
 * @return Length of copy.
 *****************************************************************************************
 */
static uint16_t app_log_strcpy(uint16_t wr_idx, uint8_t *p_log_buff, const char *p_log_data)
{
    uint16_t cpy_length = 0;

    if (!p_log_buff || !p_log_data)
    {
        return cpy_length;
    }

    while (*p_log_data != 0)
    {
        if ((wr_idx + cpy_length) < LS_BLE_LOG_LINE_BUF_SIZE)
        {
            p_log_buff[wr_idx + cpy_length] = *p_log_data++;
            cpy_length++;
        }
        else
        {
            break;
        }
    }

    return cpy_length;
}

/**
 *****************************************************************************************
 * @brief Check app log format is set or not.
 *
 * @param[in] level: App log level.
 * @param[in] fmt:   Format.
 *
 * @return Result of check.
 *****************************************************************************************
 */
static bool app_log_is_fmt_set(uint8_t level, uint8_t fmt)
{
    if (s_app_log_env.app_log_init.fmt_set[level] & fmt)
    {
        return true;
    }
    else
    {
        return false;
    }

}

/**
 *****************************************************************************************
 * @brief Transmit app log data.
 *
 * @param[in] p_data: Pointer to log data.
 * @param[in] length: Length of log data.
 *
 * @return Result of check.
 *****************************************************************************************
 */
static void app_log_data_trans(uint8_t *p_data, uint16_t length)
{
    if (NULL == p_data || 0 == length)
    {
        return;
    }

    if (s_app_log_env.trans_func)
    {
        s_app_log_env.trans_func(p_data, length);
    }

#if APP_LOG_STORE_ENABLE
    app_log_store_save(p_data, length);
#endif
}

/*
 * GLOBAL FUNCTION DEFINITIONS
 *****************************************************************************************
 */
bool ls_ble_log_init(ls_ble_log_init_t *p_log_init)
{
    if (NULL == p_log_init)
    {
        s_app_log_env.is_filter_set = false;
        memset(&s_app_log_env.app_log_init, 0, sizeof(ls_ble_log_init_t));
    }
    else if ( p_log_init->filter.level <= LOG_LVL_DEBUG)
    {
        s_app_log_env.is_filter_set = true;
        memcpy(&s_app_log_env.app_log_init, p_log_init, sizeof(ls_ble_log_init_t));
    }
    else
    {
        return false;
    }

    if(!s_app_log_env.is_output_api_cfg)
    {
#if APP_LOG_USE_BSP_CFG_ENABLE
    #if (APP_LOG_PORT == 0)
        s_app_log_env.trans_func = bsp_uart_send;
        s_app_log_env.flush_func = bsp_uart_flush;
    #elif (APP_LOG_PORT == 1)
        bsp_segger_rtt_init();
        s_app_log_env.trans_func = bsp_segger_rtt_send;
        s_app_log_env.flush_func = NULL;
    #elif (APP_LOG_PORT == 2)
        s_app_log_env.trans_func = bsp_itm_send;
        s_app_log_env.flush_func = NULL;
    #else
        return false;
    #endif
#endif
    }

    return true;
}


void ls_ble_log_output_api_cfg(ls_ble_log_trans_func_t trans_func, ls_ble_log_flush_func_t flush_func)
{
    s_app_log_env.trans_func = trans_func;
    s_app_log_env.flush_func = flush_func;
 
    s_app_log_env.is_output_api_cfg = true;
}


void ls_ble_log_output(uint8_t level, const char *tag, const char *file, const char *func, const long line, const char *format, ...)
{
    uint16_t log_length     = 0;
    uint8_t  newline_length = strlen(LS_BLE_LOG_NEWLINE_SIGN);
    int      fmt_result     = 0;
    char     line_num[LS_BLE_LOG_LINE_NB_LEN_MAX + 1]  = { 0 };
    va_list  ap;

    if (level > s_app_log_env.app_log_init.filter.level && s_app_log_env.is_filter_set)
    {
        return;
    }

#if LS_BLE_LOG_TAG_ENABLE
    /*if (!strstr(tag, s_app_log_env.app_log_init.filter.tag))
    {
        return;
    }*/
#endif

    va_start(ap, format);

    LS_BLE_LOG_LOCK();

    // Encode level info.
    if (app_log_is_fmt_set(level, LS_BLE_LOG_FMT_LVL))
    {
        log_length += app_log_strcpy(log_length, s_log_encode_buf, s_log_svt_lvl_output_info[level]);
    }

#if LS_BLE_LOG_TAG_ENABLE
    // Encode tag info.
    if (app_log_is_fmt_set(level, LS_BLE_LOG_FMT_TAG))
    {
        log_length += app_log_strcpy(log_length, s_log_encode_buf, tag);
        log_length += app_log_strcpy(log_length, s_log_encode_buf, ", ");
    }
#endif

    // Encode file directory name , function name and lune number info.
    if (app_log_is_fmt_set(level, LS_BLE_LOG_FMT_DIR | LS_BLE_LOG_FMT_FUNC | LS_BLE_LOG_FMT_LINE))
    {
        log_length += app_log_strcpy(log_length, s_log_encode_buf, "(");

        if (app_log_is_fmt_set(level, LS_BLE_LOG_FMT_DIR))
        {
            log_length += app_log_strcpy(log_length, s_log_encode_buf, file);

            if (app_log_is_fmt_set(level, LS_BLE_LOG_FMT_FUNC))
            {
                log_length += app_log_strcpy(log_length, s_log_encode_buf, " ");
            }
            else if (app_log_is_fmt_set(level, LS_BLE_LOG_FMT_LINE))
            {
                log_length += app_log_strcpy(log_length, s_log_encode_buf, ":");
            }
        }

        if (app_log_is_fmt_set(level, LS_BLE_LOG_FMT_FUNC))
        {
            log_length += app_log_strcpy(log_length, s_log_encode_buf, func);

            if (app_log_is_fmt_set(level, LS_BLE_LOG_FMT_LINE))
            {
                log_length += app_log_strcpy(log_length, s_log_encode_buf, " Line:");
            }
        }

        if (app_log_is_fmt_set(level, LS_BLE_LOG_FMT_LINE))
        {
            snprintf(line_num, LS_BLE_LOG_LINE_NB_LEN_MAX, "%ld", line);
            log_length += app_log_strcpy(log_length, s_log_encode_buf, line_num);
        }

        log_length += app_log_strcpy(log_length, s_log_encode_buf, ") ");
    }

    // Encode other log data to buffer. '\0' must be added in the end by vsnprintf. */
    fmt_result = vsnprintf((char *)s_log_encode_buf + log_length, LS_BLE_LOG_LINE_BUF_SIZE - log_length, format, ap);

    va_end(ap);

    //  Calculate log length
    if ((fmt_result > -1) && (log_length + fmt_result) <= LS_BLE_LOG_LINE_BUF_SIZE)
    {
        log_length += fmt_result;
    }
    else
    {
        log_length = LS_BLE_LOG_LINE_BUF_SIZE;
    }

    if (log_length + newline_length > LS_BLE_LOG_LINE_BUF_SIZE)
    {
        log_length = LS_BLE_LOG_LINE_BUF_SIZE;
        log_length -= newline_length;
    }

    // Encode newline sign.
    log_length += app_log_strcpy(log_length, s_log_encode_buf, LS_BLE_LOG_NEWLINE_SIGN);


    app_log_data_trans(s_log_encode_buf, log_length);

    LS_BLE_LOG_UNLOCK();
}


void ls_ble_log_raw_info(const char *format, ...)
{
    int      fmt_result = 0;
    uint16_t log_length = 0;
    va_list  ap;

    va_start(ap, format);

    LS_BLE_LOG_LOCK();

    fmt_result = vsnprintf((char *)s_log_encode_buf, LS_BLE_LOG_LINE_BUF_SIZE, format, ap);

    if ((fmt_result > -1) && (fmt_result) <= LS_BLE_LOG_LINE_BUF_SIZE)
    {
        log_length = fmt_result;
    }
    else
    {
        log_length = LS_BLE_LOG_LINE_BUF_SIZE;
    }

    app_log_data_trans(s_log_encode_buf, log_length);

    LS_BLE_LOG_UNLOCK();
}


void ls_ble_log_hex_dump(uint8_t *p_data, uint16_t length)
{
    uint16_t log_length  = 0;
    uint16_t convert_idx = 0;
    char     dump_str[8] = {0};

    LS_BLE_LOG_LOCK();

    for (convert_idx = 0; convert_idx < length; convert_idx++)
    {
        if (log_length >= LS_BLE_LOG_LINE_BUF_SIZE)
        {
            log_length = LS_BLE_LOG_LINE_BUF_SIZE;
            break;
        }

        if (p_data[convert_idx] < ' ')
        {
            s_log_encode_buf[log_length] = '.';
            log_length++;
        }
        else
        {
            snprintf(dump_str, 8, "%02X ", p_data[convert_idx]);
            log_length += app_log_strcpy(log_length, s_log_encode_buf, dump_str);
        }
    }

    app_log_data_trans(s_log_encode_buf, log_length);

    LS_BLE_LOG_UNLOCK();
}


void ls_ble_log_flush(void)
{
    if (s_app_log_env.flush_func)
    {
        s_app_log_env.flush_func();
    }
}


#if IO_REDIRECT == 0
#if defined(__CC_ARM)

struct __FILE
{
    int handle;
};

FILE __stdout;
FILE __stdin;

int fputc(int ch, FILE *file)
{
    if (s_app_log_env.trans_func)
    {
        s_app_log_env.trans_func((uint8_t *)&ch, 1);
    }

    return 1;
}

#elif defined(__GNUC__)

int _write(int file, const char *buf, int len)
{
    int tx_len = 0;

    while (tx_len < len)
    {
        if (s_app_log_env.trans_func)
        {
            s_app_log_env.trans_func((uint8_t *)buf, 1);
        }
        buf++;
        tx_len++;
    }
    return tx_len;
}

#elif defined(__ICCARM__)

size_t __write(int handle, const unsigned char *buf, size_t size)
{
    size_t len = 0;

    while (len < size)
    {
        if (s_app_log_env.trans_func)
        {
            s_app_log_env.trans_func((uint8_t *)buf, 1);
        }
        buf++;
        len++;
    }
    return len;
}

#endif /* defined(__CC_ARM) */
#endif

#endif //#if LS_BLE_LOG_ENABLE
